package ui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.util.Collection;
import java.util.Vector;

import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.ScrollPaneConstants;
import javax.swing.border.EmptyBorder;

import model.BlogPost;
import model.Sentence;
import model.Topic;
import utils.MultipleFileSelector;
import utils.SimpleText;

public class LabelingUI extends JFrame {
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	private JSplitPane mainPane;
	private JScrollPane rightPane;
	private JPanel leftPane;
	private JTextArea fullPost;
	private JTextArea sentence;
	private JTextField postID;
	private JComboBox postSentimentLabel;
	private JButton nextPost;
	private JButton prevPost;
	private JButton nextSentence;
	private JButton prevSentence;
	private JRadioButton postTopicalityLabel;
	private JRadioButton sentenceTopicalityLabel;
	private JComboBox sentenceSentimentLabel;
	private Vector<BlogPost> repo = new Vector<BlogPost>();
	private Vector<Integer> changed = new Vector<Integer>();
	private Topic topic = null;
	private Vector<String> examples = null;
	private final String sectionDiv = "\n\n\n";
	private final String subSectionDiv = "\n\n";
	private final String itemDiv = "\n";
	private final String generalDescriptionHeader = "1. General description"
			+ subSectionDiv;
	private final String topicalityRulesHeader = "2. Topicality Annotation"
			+ subSectionDiv;
	private final String topicalityGeneralInstructions = "A sentence could either be"
			+ " topical or non-topical. If the radio button is not selected, by default "
			+ "it is non-topical.\n" + subSectionDiv;
	private final String topicalityRule1 = "1) A sentence that directly mentions the"
			+ " query topic is considered topical.\n"
			+ itemDiv
			+ "For example:\n" + itemDiv;
	private final String topicalityRule2 = "2) If it can be implied from the context "
			+ "that the sentence is about the topic, the sentence should also be "
			+ "considered topical. Both sentences before and after the target sentence"
			+ " should be taken into consideration.\n"
			+ itemDiv
			+ "For example:\n" + itemDiv;
	private final String topicalityRule3 = "3) A sentence that addresses more than "
			+ "one topic, including the target topic, is still labelled as topical.\n"
			+ itemDiv + "Topical example:\n" + itemDiv;

	private final String topicalityRule4 = "4) Sentences from quoted content are processed "
			+ "in the same way as the sentences written by the author.\n"
			+ itemDiv + "Topical Example:\n" + itemDiv;
	private final String sentimentRulesHeader = "3. Sentiment Orientation Annotation"
			+ subSectionDiv;
	private final String sentimentGeneralInstructions = "A sentence can be labelled "
			+ "as showing positive, negative, mixed, or neutral opinion. Please select one"
			+ " value of these four from the dropdown menu. The default value is "
			+ "\"Unknown\".\n" + subSectionDiv;
	private final String sentimentRule1 = "1) The polarity of the opinion is evaluated without"
			+ " considering the query topic. If the sentence is about multiple topics, only "
			+ "the relevant opinion towards the target topic is considered. This is because we"
			+ " have the topicality label to help us to link the opinion with the topic, thus"
			+ " unnecessary to consider the topicality when labeling (unless multiple topics"
			+ " are found).\n" + itemDiv + "For example:\n" + itemDiv;
	private final String sentimentRule2 = "2) When the author's opinion is expressed or could be"
			+ " implied from the sentence, we label the polarity according to the author's opinion."
			+ " When it is impossible to tell what the author's opinion is, even though the sentence"
			+ " might contain opinions of someone else, we label it as neutral. This rule"
			+ " is based on the fact that many posts tend to quote statements from newspapers"
			+ " and other media but not showing any opinion of the author himself/herself. "
			+ "We assume that the user is generally interested in the author's own opinion,"
			+ " rather than quoted opinions from a third party\n"
			+ itemDiv
			+ "For example:\n" + itemDiv;
	private final String sentimentRule3 = "3) When a sentence bears opinions towards a "
			+ "statement which is related to the topic, we must take the opinion expressed in "
			+ "that statement into consideration.\n"
			+ itemDiv
			+ "For example:\n" + itemDiv;
	private final String sentimentRule4 = "4) Quoted contents are labelled as neutral, unless"
			+ " the author's opinions toward the quoted content can be implied. In that case, "
			+ "apply Rule 3 and treat the quoted content as an opinionated statement. The evidence"
			+ " of the author's stand does not have to be found in the target sentence. It is also"
			+ " acceptable if the evidence is shown in the context.\n"
			+ itemDiv + "For example:\n" + itemDiv;
	private int postIndex, sentenceIndex;
	private final int NIL = -1;

	public void initUI() {

		setTitle("Sentence Labeling System");
		setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

		// Set up the content pane.
		createLeftPane();
		createRightPane();
		mainPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, leftPane,
				rightPane);
		mainPane.setResizeWeight(0.5);
		mainPane.setOneTouchExpandable(true);
		add(mainPane);
		// Add listeners
		addListerners();

		// Load the data
		if (!isBoundToDataSource()) {
			// System.out.println("Size:" + repo.size());
			postID.setText("N//A");
			postSentimentLabel.setSelectedIndex(BlogPost.UNKNOWN);
			fullPost.setText("No data loaded. Please check the data source.");
			sentence.setText("No data loaded. Please check the data source.");
		} else {
			// System.out.println("Size:" + repo.size());
			postID.setText(repo.get(postIndex).getID());
			postSentimentLabel.setSelectedIndex(repo.get(postIndex)
					.getSentimentPolarityLabel());
			postTopicalityLabel.setSelected(repo.get(postIndex)
					.getTopicalityLabel() == BlogPost.RELEVANT ? true : false);
			fullPost.setText(repo.get(postIndex).toString());
			sentence.setText(repo.get(postIndex).getSentences().get(
					sentenceIndex).getContent().trim());
			if (repo.get(postIndex).getSentences().get(sentenceIndex)
					.getTopicality() == Sentence.RELEVANT) {
				sentenceTopicalityLabel.setSelected(true);
			} else {
				sentenceTopicalityLabel.setSelected(false);
			}
			sentenceSentimentLabel.setSelectedIndex(repo.get(postIndex)
					.getSentences().get(sentenceIndex).getSentimentPolarity());
		}

		// Display the window.
		pack();
		setVisible(true);
	}

	private void createRightPane() {

		String helpInfoText = generalDescriptionHeader
				+ topic.getTopicDescription() + "\n\n"
				+ topic.getTopicNarrative() + sectionDiv
				+ topicalityRulesHeader + topicalityGeneralInstructions
				+ topicalityRule1 + examples.get(0) + subSectionDiv
				+ topicalityRule2 + examples.get(1) + subSectionDiv
				+ topicalityRule3 + examples.get(2) + subSectionDiv
				+ topicalityRule4 + examples.get(3) + subSectionDiv
				+ sentimentRulesHeader + sentimentGeneralInstructions
				+ sentimentRule1 + examples.get(4) + subSectionDiv
				+ sentimentRule2 + examples.get(5) + subSectionDiv
				+ sentimentRule3 + examples.get(6) + subSectionDiv
				+ sentimentRule4 + examples.get(7) + subSectionDiv;

		JTextArea helpInfo = new JTextArea(helpInfoText);
		helpInfo.setEditable(false);
		helpInfo.setLineWrap(true);
		helpInfo.setWrapStyleWord(true);
		helpInfo.setBackground(new Color(248, 248, 255));
		rightPane = new JScrollPane(helpInfo);
		rightPane.setPreferredSize(new Dimension(500, 400));
	}

	private void createLeftPane() {
		fullPost = new JTextArea();
		sentence = new JTextArea();
		fullPost.setEditable(false);
		sentence.setEditable(false);
		postID = new JTextField();

		postTopicalityLabel = new JRadioButton("Not drifting");
		postSentimentLabel = new JComboBox(BlogPost.labels);
		nextPost = new JButton("Next Post");
		prevPost = new JButton("Prev Post");
		nextSentence = new JButton("Next");
		prevSentence = new JButton("Prev");
		sentenceTopicalityLabel = new JRadioButton("Topical");
		sentenceSentimentLabel = new JComboBox(Sentence.labels);
		sentenceSentimentLabel.setSelectedIndex(Sentence.UNKNOWN);

		leftPane = new JPanel();
		leftPane.removeAll();
		JScrollPane fullTextPane, sentencePane;
		JPanel postTextPane, postLabelPane, buttonPane;
		fullTextPane = new JScrollPane(fullPost);
		sentencePane = new JScrollPane();
		postLabelPane = new JPanel();
		buttonPane = new JPanel();
		postTextPane = new JPanel();

		// Setting up the post panel
		JPanel infoPane = new JPanel(new BorderLayout());
		infoPane.setBorder(new EmptyBorder(5, 5, 5, 5));
		fullTextPane
				.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
		fullTextPane
				.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
		fullPost.setLineWrap(true);
		fullTextPane.setPreferredSize(new Dimension(700, 400));
		postTextPane.setLayout(new BorderLayout());
		postTextPane.add(fullTextPane, BorderLayout.CENTER);
		postTextPane.setBorder(new EmptyBorder(5, 5, 5, 5));
		infoPane.add(postID, BorderLayout.WEST);

		// postLabelPane.add(postTopicalityLabel);
		// postLabelPane.add(postSentimentLabel);
		// infoPane.add(postLabelPane, BorderLayout.EAST);
		postTextPane.add(infoPane, BorderLayout.NORTH);

		// Setting up the sentence panel
		sentencePane
				.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
		sentencePane
				.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
		sentencePane.setViewportView(sentence);
		sentence.setLineWrap(true);
		sentencePane.setBorder(new EmptyBorder(5, 5, 5, 5));
		sentencePane.setPreferredSize(new Dimension(700, 100));

		// Setting up the button panel
		JPanel sentenceButtonPane = new JPanel();
		sentenceButtonPane.add(sentenceTopicalityLabel);
		sentenceButtonPane.add(sentenceSentimentLabel);
		sentenceButtonPane.add(prevSentence);
		sentenceButtonPane.add(nextSentence);

		buttonPane.setLayout(new BorderLayout());
		buttonPane.add(sentenceButtonPane, BorderLayout.CENTER);
		buttonPane.add(prevPost, BorderLayout.WEST);
		buttonPane.add(nextPost, BorderLayout.EAST);

		// Adding panels to the main frame
		leftPane.setLayout(new GridBagLayout());
		GridBagConstraints constraints = new GridBagConstraints();
		Insets insets = new Insets(5, 5, 5, 5);
		constraints.fill = GridBagConstraints.HORIZONTAL;
		constraints.gridx = 0;
		constraints.gridy = 0;
		constraints.insets = insets;
		leftPane.add(postTextPane, constraints);

		constraints.gridy = 1;
		leftPane.add(sentencePane, constraints);

		constraints.gridwidth = 4;
		constraints.gridx = 0;
		constraints.gridy = 2;
		leftPane.add(buttonPane, constraints);

	}

	public void bindDataSource(Collection<BlogPost> posts, Topic topic,
			Vector<String> examples) {
		repo.addAll(posts);
		this.topic = topic;
		this.examples = examples;
	}

	public void bindDataSourceByPath(String directory) {
		repo.clear();// Clear whatever was in the repository
		MultipleFileSelector selector = new MultipleFileSelector(directory);
		System.err.println(selector.getAllFileStrings(".*txt").size()
				+ " files selected.");
		for (String fileStr : selector.getAllFileStrings(".*txt")) {
			System.err.println("Debug : " + fileStr);
			SimpleText text = new SimpleText();
			text.readFromFile(new File(fileStr));
			text.replaceString("\";", "\"");
			text.replaceString("~~~~~~~~~~~~", "\"");
			text.replaceString("~~~~~~~~", "");
			text.replaceString("~~~~", "'");
			text.replaceString("	", " ");
			text.replaceString("  ", " ");
			text.removeString("\r");
			text.replaceString("\n ", "\n");
			text.replaceString(" \n", "\n");
			text.replaceString("\n\n", "\n");
			text.replaceString(" .", ".");
			System.out.println("###" + text.getText() + "###");
			repo.add(new BlogPost(fileStr, text.getText().trim()));
		}
		System.err.println(this.getRepo().size()
				+ " files bound to the data handler.");
	}

	public void addListerners() {
		nextPost.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				if (postIndex < repo.size() - 1) {
					if (!changed.contains(postIndex) && postIndex > -1) {
						changed.add(postIndex);
					}
					if (!changed.contains(postIndex + 1)
							&& postIndex < (repo.size() - 1)) {
						changed.add(postIndex + 1);
					}
					postIndex++;
					sentenceIndex = 0; // Reset sentence index after changing
					// post.
					sentence.setText(null);
					fullPost.setText(repo.get(postIndex).toString());
					sentence.setText(repo.get(postIndex).getSentences().get(
							sentenceIndex).getContent().trim());
					postID.setText(repo.get(postIndex).getID());
					postTopicalityLabel.setSelected(repo.get(postIndex)
							.getTopicalityLabel() == BlogPost.RELEVANT ? true
							: false);
					postSentimentLabel.setSelectedIndex(repo.get(postIndex)
							.getSentimentPolarityLabel());
					setLabels();
				} else {
					postIndex = repo.size();
					fullPost
							.setText("We've reached the last post in the collection");
					sentence.setText("");
					setLabels();
				}
			}
		});

		prevPost.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				if (postIndex > 0) {
					if (!changed.contains(postIndex) && postIndex < repo.size()) {
						changed.add(postIndex);
					}
					if (!changed.contains(postIndex - 1) && postIndex > 0) {
						changed.add(postIndex - 1);
					}
					postIndex--;
					sentenceIndex = 0; // Reset sentence index after changing
					// post.
					fullPost.setText(repo.get(postIndex).toString());
					sentence.setText(repo.get(postIndex).getSentences().get(
							sentenceIndex).getContent().trim());
					postID.setText(repo.get(postIndex).getID());
					postSentimentLabel.setSelectedIndex(repo.get(postIndex)
							.getSentimentPolarityLabel());
					postTopicalityLabel.setSelected(repo.get(postIndex)
							.getTopicalityLabel() == BlogPost.RELEVANT ? true
							: false);
					setLabels();
				} else {
					postIndex = NIL;
					fullPost
							.setText("We've reached the first post in the collection");
					sentence.setText("");
					setLabels();
				}
			}
		});

		nextSentence.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				if (isValidPost()) {
					if (sentenceIndex < repo.get(postIndex).getSentences()
							.size() - 1) {
						saveLabels();
						sentenceIndex++;
						sentence.setText(repo.get(postIndex).getSentences()
								.get(sentenceIndex).getContent().trim());
						setLabels();
					} else {
						saveLabels();
						sentenceIndex = repo.get(postIndex).getSentences()
								.size();
						sentence
								.setText("We've reached the last sentence in the post");
						setLabels();
					}
				}

			}
		});

		prevSentence.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				if (isValidPost()) {
					if (sentenceIndex > 0) {
						saveLabels();
						sentenceIndex--;
						sentence.setText(repo.get(postIndex).getSentences()
								.get(sentenceIndex).getContent().trim());
						setLabels();
					} else {
						saveLabels();
						sentenceIndex = NIL;
						sentence
								.setText("We've reached the first sentence in the post");
						setLabels();
					}
				}
			}
		});

		postTopicalityLabel.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				if (isValidPost()) {
					repo
							.get(postIndex)
							.setTopicality(
									postTopicalityLabel.isSelected() ? BlogPost.RELEVANT
											: BlogPost.IRRELEVANT);
				}
			}
		});

		postSentimentLabel.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				if (isValidPost()) {
					repo.get(postIndex).setSentimentPolarityLabel(
							postSentimentLabel.getSelectedIndex());
				}

			}

		});

		sentenceTopicalityLabel.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				if (isValidPost() && isValidSentence()) {
					Sentence thisSentence = repo.get(postIndex).getSentences()
							.get(sentenceIndex);
					if (!sentenceTopicalityLabel.isSelected()) {
						thisSentence.setTopicality(Sentence.IRRELEVANT);
						thisSentence.setSentimentPolarity(Sentence.UNKNOWN);
						sentenceSentimentLabel
								.setSelectedIndex(Sentence.UNKNOWN);
					} else {
						sentenceSentimentLabel.showPopup();
					}
				}
			}
		});

		sentenceSentimentLabel.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				if (isValidPost() && isValidSentence()) {
					Sentence thisSentence = repo.get(postIndex).getSentences()
							.get(sentenceIndex);
					thisSentence.setSentimentPolarity(sentenceSentimentLabel
							.getSelectedIndex());
					thisSentence.setTopicality(Sentence.RELEVANT);
					if (sentenceSentimentLabel.getSelectedIndex() != Sentence.UNKNOWN) {
						sentenceTopicalityLabel.setSelected(true);
					} else {
						sentenceTopicalityLabel.setSelected(false);
					}
				}
			}
		});

		postID.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				int targetPostIndex = getBlogPostByID(postID.getText());
				if (targetPostIndex == NIL) {
					postID.setText("N//A");
					postSentimentLabel.setSelectedIndex(BlogPost.UNKNOWN);
					fullPost
							.setText("No post matching the post ID. Please check the data source.");
					sentence
							.setText("No data loaded. Please check the data source.");
				} else {
					postIndex = targetPostIndex;
					if (!changed.contains(postIndex)) {
						changed.add(postIndex);
					}
					sentenceIndex = 0;
					fullPost.setText(repo.get(postIndex).toString());
					sentence.setText(repo.get(postIndex).getSentences().get(
							sentenceIndex).getContent().trim());
					postID.setText(repo.get(postIndex).getID());
					postSentimentLabel.setSelectedIndex(repo.get(postIndex)
							.getSentimentPolarityLabel());
					postTopicalityLabel.setSelected(repo.get(postIndex)
							.getTopicalityLabel() == BlogPost.RELEVANT ? true
							: false);
					setLabels();
				}
			}

		});
	}

	public void setLabels() {
		if (isValidPost() && isValidSentence()) {
			sentenceTopicalityLabel.setEnabled(true);
			sentenceSentimentLabel.setEnabled(true);
			int topicality = repo.get(postIndex).getSentences().get(
					sentenceIndex).getTopicality();
			if (topicality == Sentence.RELEVANT) {
				sentenceTopicalityLabel.setSelected(true);
			} else if (topicality == Sentence.IRRELEVANT) {
				sentenceTopicalityLabel.setSelected(false);
			}
			int sentimentPolarity = repo.get(postIndex).getSentences().get(
					sentenceIndex).getSentimentPolarity();
			sentenceSentimentLabel.setSelectedIndex(sentimentPolarity);
		} else {
			sentenceTopicalityLabel.setEnabled(false);
			sentenceSentimentLabel.setEnabled(false);
		}
	}

	public void saveLabels() {
		if (isValidPost() && isValidSentence()) {
			Sentence thisSentence = repo.get(postIndex).getSentences().get(
					sentenceIndex);
			if (sentenceTopicalityLabel.isSelected()) {
				thisSentence.setSentimentPolarity(sentenceSentimentLabel
						.getSelectedIndex());
				thisSentence.setTopicality(Sentence.RELEVANT);
			} else {
				thisSentence.setSentimentPolarity(Sentence.UNKNOWN);
				thisSentence.setTopicality(Sentence.IRRELEVANT);
			}
		}
	}

	/**
	 * Returns the status of the data handler.
	 * 
	 * @return whether the data source is bound or not
	 */
	public boolean isBoundToDataSource() {
		return repo.size() == 0 || topic == null ? false : true;
	}

	/**
	 * Reset the data source to an empty collection
	 */
	public void resetDataSource() {
		repo = new Vector<BlogPost>();
	}

	public boolean isValidPost() {
		return postIndex > NIL && postIndex < repo.size() ? true : false;
	}

	public boolean isValidSentence() {
		return sentenceIndex > NIL
				&& sentenceIndex < repo.get(postIndex).getSentences().size() ? true
				: false;
	}

	public int getBlogPostByID(String postID) {
		int index = 0;
		for (; index < repo.size(); index++) {
			if (repo.get(index).getID().equals(postID)) {
				return index;
			}
		}
		return NIL;
	}

	public Vector<BlogPost> getRepo() {
		return repo;
	}

	public Vector<Integer> getChanged() {
		return changed;
	}
}
